﻿using System;
using Pfz.Threading;

namespace Pfz.Collections
{
	/// <summary>
	/// This class connects to a EnumeratorDistributor and is able
	/// to use GetNext to get a next frame when one is available, while it is 
	/// also able to "loose" values if the real enumerator is running faster 
	/// than this client. This is useful when getting frames from a web-cam, 
	/// for example.
	/// </summary>
	public class EnumeratorDistributorClient<T>:
		ThreadSafeDisposable,
		IFastEnumerator<T>
	where
		T: class
	{
		internal ManagedAutoResetEvent _autoResetEvent = new ManagedAutoResetEvent();
		
		#region Constructor
			/// <summary>
			/// Creates a new multi-client enumerator connected to the given distributor.
			/// </summary>
			public EnumeratorDistributorClient(EnumeratorDistributor<T> distributor)
			{
				if (distributor == null)
					throw new ArgumentNullException("distributor");
				
				Distributor = distributor;
				lock(distributor.DisposeLock)
				{
					distributor.CheckUndisposed();
					distributor._clientEnumerators.Add(this);
				}
			}
		#endregion
		#region Dispose
			/// <summary>
			/// Releases the resources used by this enumerator and removes it from
			/// the distributor list.
			/// </summary>
			protected override void Dispose(bool disposing)
			{
				if (disposing)
				{
					var distributor = Distributor;
					if (distributor != null)
					{
						Distributor = null;
						
                        lock(distributor.DisposeLock)
						{
							var clientEnumerators = distributor._clientEnumerators;
							if (clientEnumerators != null)
								clientEnumerators.Remove(this);
						}
					}
					
					Disposer.Dispose(ref _autoResetEvent);
				}
			
				base.Dispose(disposing);
			}
		#endregion
		
		#region Property - Distributor
			/// <summary>
			/// Gets the Distributor used by this enumerator.
			/// </summary>
			public EnumeratorDistributor<T> Distributor { get; private set; }
		#endregion
		#region Method - GetNext
			/// <summary>
			/// Gets the actual value of the distributor or waits until a new
			/// value is available.
			/// </summary>
			public virtual T GetNext()
			{
                var autoResetEvent = _autoResetEvent;
                if (autoResetEvent == null)
                    return null;

                autoResetEvent.WaitOne();

                var distributor = Distributor;
                if (distributor == null)
                    return null;

                lock(distributor.DisposeLock)
				{
                    if (distributor.WasDisposed)
					{
						var disposeException = distributor.DisposeException;
						if (disposeException != null)
							throw new ObjectDisposedException("This enumerator was disposed by an exception in its distributor.", disposeException);

						return null;
					}

					return distributor.ActualValue;
				}
			}
		#endregion
	}
}
